package edu.iit.cs553;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.StartInstancesRequest;

public class Provisioner {

	private AmazonEC2Client ec2Client;
	private CloudWatchMonitor monitor;
	private List<Map<String, Integer>> instancesIdsAndStates;

	public Provisioner()
	{
		this.ec2Client = new AmazonEC2Client(new ClasspathPropertiesFileCredentialsProvider());
		this.monitor = new CloudWatchMonitor();
		ec2Client.setEndpoint("ec2.us-west-2.amazonaws.com");
		this.instancesIdsAndStates = new ArrayList<Map<String, Integer>>();
		initInstancesIdsAndStates();
	}

	public void allocateNeededWorkersUsingCloudWatchMonitor()
	{
		getInstancesState();
		int numberOfTasksInQueue = monitor.getNumberOfTasksInQueue();
		System.out.println("Number of tasks in queue: " + numberOfTasksInQueue);
		for (int i = 0; i < numberOfTasksInQueue; i++)
		{
			boolean additionalWorkerAllocated = allocateAdditionalWorker();
			if (additionalWorkerAllocated == true)
			{
				System.out.println("An additional worker has been allocated");
			} else
			{
				System.out.println("All workers are busy!");
			}
		}
	}

	public void allocateNeededWorkersWithoutCloudWatch(int numberOfTasksInQueue)
	{
		getInstancesState();
		System.out.println("\nNumber of tasks in queue: " + numberOfTasksInQueue + "\n");
		for (int i = 0; i < numberOfTasksInQueue; i++)
		{
			boolean additionalWorkerAllocated = allocateAdditionalWorker();
			if (additionalWorkerAllocated == true)
			{
				System.out.println("An additional worker has been allocated");
			} else
			{
				System.out.println("All workers are busy!");
			}
		}
	}

	/**
	 * Allocates a new worker if at least one is not working
	 * 
	 * @return true if a new worker has been allocated, false if couldn't
	 *         allocate a new worker because everyone is working
	 */
	private boolean allocateAdditionalWorker()
	{
		boolean additionalWorkerAllocated = false;
		for (Map<String, Integer> instanceIdAndState : this.instancesIdsAndStates)
		{
			String instanceId = getInstanceId(instanceIdAndState);
			int instanceState = instanceIdAndState.get(instanceId);

			if (instanceState == 0)
			{
				launchInstance(instanceId);
				additionalWorkerAllocated = true;
				return additionalWorkerAllocated;
			}
		}
		return additionalWorkerAllocated;
	}

	private void launchInstance(String instanceId)
	{
		StartInstancesRequest startInstancesRequest = new StartInstancesRequest();
		List<String> instancesIdsToLaunch = new ArrayList<String>();
		instancesIdsToLaunch.add(instanceId);
		startInstancesRequest.setInstanceIds(instancesIdsToLaunch);
		ec2Client.startInstances(startInstancesRequest);
		updateInstanceState(instanceId, "running");
	}

	private void getInstancesState()
	{
		DescribeInstancesResult describeInstancesResult = ec2Client.describeInstances();
		List<Reservation> reservations = describeInstancesResult.getReservations();
		for (Reservation reservation : reservations)
		{
			List<Instance> instances = reservation.getInstances();
			for (Instance instance : instances)
			{
				String instanceId = instance.getInstanceId();
				String instanceStateInString = instance.getState().getName();
				updateInstanceState(instanceId, instanceStateInString);
			}
		}
	}

	private void updateInstanceState(String instanceId, String instanceStateInString)
	{
		for (Map<String, Integer> instanceIdAndState : this.instancesIdsAndStates)
		{
			int instanceState = getInstanceStateFromStateInString(instanceStateInString);
			String currentInstanceId = getInstanceId(instanceIdAndState);
			if (instanceId.equals(currentInstanceId))
			{
				instanceIdAndState.put(currentInstanceId, instanceState);
			}
		}
	}

	private int getInstanceStateFromStateInString(String instanceStateInString)
	{
		int state = 0;
		if (instanceStateInString.equals("running"))
		{
			state = 1;
		}
		return state;
	}

	private String getInstanceId(Map<String, Integer> instanceIdAndState)
	{
		Set<String> keySet = instanceIdAndState.keySet();
		Iterator<String> iterator = keySet.iterator();
		String instanceId = iterator.next();
		return instanceId;
	}

	private void initInstancesIdsAndStates()
	{
		List<String> instancesIds = initInstancesIds();
		for (String instanceId : instancesIds)
		{
			Map<String, Integer> instanceIdAndState = new HashMap<String, Integer>();
			instanceIdAndState.put(instanceId, 0);
			this.instancesIdsAndStates.add(instanceIdAndState);
		}
		getInstancesState();
	}

	private List<String> initInstancesIds()
	{
		DescribeInstancesResult describeInstancesResult = ec2Client.describeInstances();
		List<Reservation> reservations = describeInstancesResult.getReservations();
		List<String> instancesIds = new ArrayList<String>();
		for (Reservation reservation : reservations)
		{
			List<Instance> instances = reservation.getInstances();
			for (Instance instance : instances)
			{
				String instanceId = instance.getInstanceId();
				instancesIds.add(instanceId);
			}
		}
		return instancesIds;
	}

	// Used for debugging purposes
	public void displayInstancesStates()
	{
		for (Map<String, Integer> instanceIdAndState : this.instancesIdsAndStates)
		{
			String instanceId = getInstanceId(instanceIdAndState);
			int instanceState = instanceIdAndState.get(instanceId);
			System.out.println(instanceId + ": " + instanceState);
		}
	}
}
